<?php

/**
 * Description of sphinx
 *
 * @author libaoling
 * @time 2015-2-26
 */
class Sphinx
{

    private $sphinx;
    private $datasize;

    /** 初始化
     * @param Array $config 配置 格式: array('ip','port','timeout','datasize')
     */
    public function __construct($config = array())
    {

        require(dirname(__FILE__) . '/sphinxapi.php');
        $configFile = dirname(__FILE__) . '/sphinx.inc.php';
        $sphinxConfig = file_exists($configFile) ? require($configFile) : array();

        $server = isset($config['ip']) ? $config['ip'] : $sphinxConfig['ip'];
        $port = isset($config['port']) ? $config['port'] : $sphinxConfig['port'];
        $timeout = isset($config['timeout']) ? $config['timeout'] : $sphinxConfig['timeout'];
        $datasize = isset($config['datasize']) ? $config['datasize'] : $sphinxConfig['datasize'];
        $this->datasize = $datasize;

        $this->sphinx = new SphinxClient();

        //设置连接Sphinx主机名与端口
        $this->sphinx->SetServer($server, $port);

        //连接失效时间
        $this->sphinx->SetConnectTimeout($timeout);

        //设置返回结果集为php数组格式
        $this->sphinx->SetArrayResult(true);

        /**
         * 匹配模式:
         * SPH_MATCH_ALL，匹配所有查询词（缺省模式）
         * SPH_MATCH_ANY，匹配任意查询词
         * SPH_MATCH_PHRASE，短语匹配
         * SPH_MATCH_BOOLEAN，布尔表达式匹配
         * SPH_MATCH_EXTENDED，查询匹配一个Sphinx内部查询语言表达式
         * SPH_MATCH_EXTENDED2, 使用第二版的“扩展匹配模式”对查询进行匹配.
         * SPH_MATCH_FULLSCAN, 强制使用下文所述的“完整扫描”模式来对查询进行匹配。
         *   注意，在此模式下，所有的查询词都被忽略，尽管过滤器、过滤器范围以及分组仍然起作用，但任何文本匹配都不会发生.
         * 
         *  设置扩展匹配模式
         *  $sphinx->SetMatchMode ( "SPH_MATCH_EXTENDED2" );
         *  查询中使用条件语句，字段用@开头，搜索内容包含测试，toid等于1的邮件：
         *  $result = $sphinx->query('@content (测试) & @toid =1', '*');
         *  用括号和&（与）、|、（或者）、-（非，即!=）设置更复杂的条件
         *  $result = $sphinx->query('(@content (测试) & @subject =呃) | (@fromid -(100))', '*');
         */
        $this->sphinx->SetMatchMode(SPH_MATCH_ANY);

        //匹配结果的偏移量，参数的意义依次为：起始位置，返回结果条数，最大匹配条数
        $this->sphinx->SetLimits(0, $this->datasize, $this->datasize);

        //最大搜索时间
        $this->sphinx->SetMaxQueryTime(50);
    }

    /**
     * 获取查询結果
     * @param	Mixed	$keyword	查询的关键字 String or Array
     * @param	String	$source		配置源，如多个用英文,分隔
     * @param	Array	$param		array(
     * 									'filter'        =>	'',	搜索条件     Array
     * 									'filterRange'	=>	'',	搜索条件范围	Array(array('字段',from,to))
     * 									'weight'        =>	'',	搜索比重	    Array
     * 									'groupby'       =>	'',	筛选重复	    String or Array
     * 									'sort'          =>	'',	排序		    String or Array
     * 									'limit'         =>	''	取记录数	    String or Array
     * 								);
     * @return	Array
     */
    public function getSphinx($keyword, $source, $param = array())
    {
        if (empty($keyword) || empty($source))
            return false;

        $filter = isset($param['filter']) && is_array($param['filter']) ? $param['filter'] : array();
        $filterAnd = isset($param['filterAnd']) && is_array($param['filterAnd']) ? $param['filterAnd'] : array();
        $filterRange = isset($param['filterRange']) && is_array($param['filterRange']) ? $param['filterRange'] : array();
        $filterFloatRange = isset($param['filterFloatRange']) && is_array($param['filterFloatRange']) ? $param['filterFloatRange'] : array();
        $weight = isset($param['weight']) && is_array($param['weight']) ? $param['weight'] : array();
        $groupby = isset($param['groupby']) ? $param['groupby'] : '';
        $sort = isset($param['sort']) ? $param['sort'] : '';
        $limit = isset($param['limit']) ? $param['limit'] : false;

        /**
         * 设定过滤条件$attribute是属性名，相当于字段名（用SPH_MATCH_EXTENDED时），$value是值，$exclude是布尔型，
         * 当为true时，相当于$attribute!=$value，默认值是false
         * $this->sphinx->SetFilter($attribute, $values, $exclude);
         */
        if ($filter)
            foreach ($filter as $key => $val)
                $this->sphinx->SetFilter($key, $val);
        
        if ($filterAnd)
            foreach ($filterAnd as $key => $vals)
                foreach ($vals as $val)
                    $this->sphinx->SetFilter($key, array($val));
        
        /**
         * 指定一个值的范围
         * 
         * $this->sphinx->SetFilterRange('toid', 5, 200,$boolean);    //toid的值在5-200之间
         * $this->sphinx->SetFilterRange('toid', 5, 200,false);    //toid的值在5-200之外
         * 
         * 大于等于某一时间截$time
         *  $this->sphinx->SetFilterRange('sendtime', $time, 10000000000) //时间截最大是10个9，再加1是不可超越了。。
         * 大于某一时间截$time
         *  $this->sphinx->SetFilterRange('sendtime', $time+1, 10000000000)
         * 小于等于某一时间截$time
         *  $this->sphinx->SetFilterRange('sendtime', -1, $time)    //时间截最小是0，所以应该减1
         * 大于某一时间截$time
         *  $this->sphinx->SetFilterRange('sendtime', -1, $time - 1)
         */
        if ($filterRange)
            foreach ($filterRange as $key => $v){
                if(!isset($v['max']) || !$v['max'])
                    $v['max'] = 10000000000;
                if(!isset($v['min']) || !$v['min'])
                    $v['min'] = -1;
                $this->sphinx->SetFilterRange($key, $v['min'], $v['max']);
            }
            
        if ($filterFloatRange)
            foreach ($filterFloatRange as $key => $v){
                if(!isset($v['max']) || $v['max'] <= 0)
                    $v['max'] = floatval(10000000000);
                if(!isset($v['min']) || $v['min'] <= 0)
                    $v['min'] = floatval('0.00');
                $this->sphinx->SetFilterFloatRange($key, $v['min'], $v['max']);
            }

        /**
         * 可选，为每一个全文检索字段设置权重，主要根据你在sql_query中定义的字段的顺序，
         * 可以按字段名称来设定权重 
         * $this->sphinx->SetFieldWeights(array('title'=>100, 'content'=>5));
         */
        $weight && $this->sphinx->SetFieldWeights($weight);

        /**
         * 设定group by
         * 
         * 根据分组方法，匹配的记录集被分流到不同的组，每个组都记录着组的匹配记录数以及根据当前排序方法本组中的最佳匹配记录。
         * 最后的结果集包含各组的一个最佳匹配记录，和匹配数量以及分组函数值
         * 结果集分组可以采用任意一个排序语句，包括文档的属性以及sphinx的下面几个内部属性
         * @id--匹配文档ID
         * @weight, @rank, @relevance--匹配权重
         * @group--group by 函数值
         * @count--组内记录数量
         * $groupsort的默认排序方法是@group desc，就是按分组函数值大小倒序排列
         * $this->sphinx->SetGroupBy($attribute, $func, $groupsort);
         */
        if ($groupby)
            if (is_array($groupby)) {
                if (count($groupby) == 3)
                    $this->sphinx->SetGroupBy($groupby[0], $groupby[1], $groupby[2]);
            }
            else
                $this->sphinx->SetGroupBy($groupby, SPH_GROUPBY_ATTR, '@weight DESC');

        /**
         * 设定order by的内容
         * 
         * 第一个参数是排序方法名，值有
         *  SPH_SORT_RELEVANCE 模式, 按相关度降序排列（最好的匹配排在最前面）
         *  SPH_SORT_ATTR_DESC 模式, 按属性降序排列 （属性值越大的越是排在前面）
         *  SPH_SORT_ATTR_ASC 模式, 按属性升序排列（属性值越小的越是排在前面）
         *  SPH_SORT_TIME_SEGMENTS 模式, 先按时间段（最近一小时/天/周/月）降序，再按相关度降序
         *  SPH_SORT_EXTENDED 模式, 按一种类似SQL的方式将列组合起来，升序或降序排列。
         *  SPH_SORT_EXPR 模式，按某个算术表达式排序
         * $sortby的值如"hits desc"
         */
        if ($sort)
            if (is_array($sort)) {
                if (count($sort) == 2)
                    $this->sphinx->SetSortMode($sort[0], $sort[1]);
            }
            else
                $this->sphinx->SetSortMode(SPH_SORT_RELEVANCE, $sort);

        /**
         * 相当于mysql的limit $offset,$limit
         */
        if ($limit){
            if (is_array($limit)) {
                if (count($limit) == 2)
                    $this->sphinx->SetLimits($limit[0], $limit[1],  $this->datasize);
            }
            else
                $this->sphinx->SetLimits(0, $limit);
        }else
            $this->sphinx->SetLimits(0, $this->datasize);

        /**
         * $keyword是查询的关键字，$source是索引名称，当等于*时表查询所有索引
         */
        if (is_array($keyword)){
            $key = implode("", $keyword);
//            foreach ($keyword as $key)
            $this->sphinx->AddQuery($key, $source);
        }
        else
            $this->sphinx->AddQuery($keyword, $source);


        $result = $this->sphinx->RunQueries();

        $this->sphinx->ResetFilters();
        $this->sphinx->ResetGroupBy();

        return $result;
    }

}